// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using Kailua.net.windward.utils;
using net.windward.utils.ado.Access;
using net.windward.utils.ado.DB2;
using net.windward.utils.ado.Excel;
using net.windward.utils.ado.MySql;
using net.windward.utils.ado.ODBC;
using net.windward.utils.ado.OleDb;
using net.windward.utils.ado.Oracle;
using net.windward.utils.ado.PostgreSql;
using Kailua.net.windward.utils.ado.SPList.custom;
using net.windward.utils.ado.SqlServer;
using net.windward.utils.ado.Unknown;
using net.windward.utils.ado.SPList;

namespace net.windward.utils.ado
{
	/// <summary>
	/// This wraps DbProviderFactory and provides additional functionality for each provider. All properties to list
	/// if a provider can perform any task in it's child classes is provider here, not in the child classes. This
	/// class can return IWrServer objects for a given server/instance that exists on a computer. The ODBC/OleDb
	/// concept of a Provider (which is different from a DbProviderFactory) is handled as a property set in the
	/// WrProviderFactory class so all other cases do not need to implement a class for that layer.
	/// This is a class and not an interface because it must extend DbProviderFactory.
	/// </summary>
	public static class WrProviderFactories
	{

		#region factories

		/// <summary>
		/// Returns all providers that have drivers registered with .NET on this computer.
		/// </summary>
		static public ProviderInfo GetAllProviders()
		{
			try
			{
				ArrayList vendors = new ArrayList();
				ArrayList problems = new ArrayList();
				DataTable tbl = DbProviderFactories.GetFactoryClasses();
				foreach (DataRow row in tbl.Rows)
				{
					string providerClass = (string)row[2];
					try
					{
						DbProviderFactories.GetFactory(providerClass); // will throw exception if not configured properly
						vendors.Add(CreateProvider((string)row[0], row[1] as string, providerClass));
					}
					catch (Exception ex)
					{
						problems.Add(new BadProvider(providerClass, ex));
					}
				}

				// add in the ODBC ones we provide as seperate drivers
				for (int ind = 0; ind < 4; ind++ )
					try
					{
						WrVendor vendor = null;
						switch (ind)
						{
							case 0:
								vendor = new WrAccessVendor(WrAccessVendor.FactoryName, "Microsoft Access (via ODBC)", WrAccessVendor.FactoryClass);
								break;
							case 1:
								vendor = new WrExcelVendor(WrExcelVendor.FactoryName, "Microsoft Excel (via OleDb)", WrExcelVendor.FactoryClass);
								break;
							case 2:
								vendor = new WrSPListVendor(WrSPListVendor.FactoryName, "Microsoft SharePoint List (via OleDb)", WrSPListVendor.FactoryClass);
								break;
							case 3:
								vendor = new WrSPListVendor2(WrSPListVendor2.FactoryDispName, "SharePoint List", WrSPListVendor2.FactoryClass);
								break;
						}
						if ((vendor != null) && vendor.IsInstalled)
							vendors.Add(vendor);
					}
					catch
					{
						Trap.trap();
					}

				vendors.Sort();
				return new ProviderInfo((WrVendor[])vendors.ToArray(typeof(WrVendor)), (BadProvider[])problems.ToArray(typeof(BadProvider)));
			}
			catch (Exception)
			{
				return new ProviderInfo(new WrVendor[0], new BadProvider[0]);
			}
		}

		/// <summary>
		/// Will try to get the factory class from a passed in type that is a member class of the factory.
		/// </summary>
		/// <param name="typeMember">member class.</param>
		/// <returns>matching DbProviderFactory. null if can't find it.</returns>
		public static DbProviderFactory GetProviderFromMember(Type typeMember)
		{

			string typeName = typeMember.FullName;
			int pos = typeName.LastIndexOf('.');
			if (pos != -1)
				typeName = typeName.Substring(0, pos);
			string typeStart = typeName + '.';
			DataTable tabl = DbProviderFactories.GetFactoryClasses();
			foreach (DataRow row in tabl.Rows)
			{
				try
				{
					string providerClass = (string) row[2];
					if ((providerClass == typeName) || providerClass.StartsWith(typeStart))
						return DbProviderFactories.GetFactory(providerClass);
				}
				catch (Exception)
				{
					// keep looking.
				}
			}
			Trap.trap();
			return null;
		}

		/// <summary>
        /// <para>Creates a provider wrapping the ADO.NET generated provider.</para>
        /// <para>See the <see cref="DbProviderFactories.GetFactoryClasses"/> method. It returns a DataTable that contains
        /// the information about each provider factory. <paramref name="name"/>, <paramref name="description"/>, and
        /// <paramref name="providerClass"/> map directly to the Name, Description, and InvariantName columns, respectively.
        /// Name is for display, and InvariantName is for programmatic reference to the data provider. However, for access,
        /// excel, and sharepoint list vendors, the <paramref name="name"/> parameter will determine the vendor.</para>
		/// </summary>
        /// <param name="name">The friendly name of the provider (vendor name). Must not be null. This determines the provider 
        /// for access, excel, and sharepoint list.</param>
		/// <param name="description">The description of this provider (optional).</param>
        /// <param name="providerClass">The class of the provider. Must not be null. This determines the provider for most 
        /// vendors except access, excel, and sharepoint list.</param>
		/// <returns></returns>
		public static WrVendor CreateProvider(string name, string description, string providerClass)
		{
            /*
             * Create Provider uses the string determined by DetermineProvider to identify the
             * provider and determine the appropriate WrVendor to generate.  For Access, Excel,
             * and SharePoint list data sources, this string corresponds to WrVendor.ProviderSubtype.
             */

			// clean up the name
			name = name.Trim();
			if (name.ToLower().StartsWith("sqlclient"))
				name = "SqlServer" + name.Substring(9);
			name = name.Replace("Npgsql", "PostgreSQL");
			if (name.ToLower().EndsWith(".net data provider"))
				name = name.Substring(0, name.Length - 18).Trim() + " Database";
			else
				if (name.ToLower().EndsWith("data provider"))
					name = name.Substring(0, name.Length - 13).Trim() + " Database";

			switch (DetermineProvider(name, description, providerClass))
			{
				case "Access":
					return new WrAccessVendor(name, description, providerClass);
				case "DB2":
					return new WrDB2Vendor(name, description, providerClass);
                case "Excel":
                    return new WrExcelVendor(name, description, providerClass);
				case "MySql":
					return new WrMySqlVendor(name, description, providerClass);
				case "ODBC":
					return new WrOdbcVendor(name, description, providerClass);
				case "OleDb":
					return new WrOleDbVendor(name, description, providerClass);
				case "Oracle":
					return new WrOracleVendor(name, description, providerClass);
				case "PostgreSql":
					return new WrPostgreSqlVendor(name, description, providerClass);
                case WrSPListVendor2.PROVIDER_SUBTYPE:
                    return new WrSPListVendor2(name, description, providerClass);
                case "SPListOleDb":
                    return new WrSPListVendor(name, description, providerClass);
				case "SqlServer":
					return new WrSqlServerVendor(name, description, providerClass);
			}
			return new WrUnknownVendor(name, description, providerClass);
		}

		///<summary>
		/// Determine which known vendor this is.
		///</summary>
		///<param name="providerClass">Usually the DB provider class. Use vendor name for access, excel, and sharepoint list.</param>
		///<returns>DB2, MySql, ODBC, OleDb, Oracle, PostgreSql, SqlServer, Access, Excel, SPListOleDb, SPList, or null.</returns>
		public static string DetermineProvider(string providerClass)
		{

			providerClass = providerClass.ToLower().Trim();
            if (providerClass == "access (odbc)")
                return "Access";
            if (providerClass == "excel (oledb)")
                return "Excel";
            if (providerClass == "sharepoint list (oledb)")
                return "SPListOleDb";
            if (providerClass == "sharepoint list")
                return WrSPListVendor2.PROVIDER_SUBTYPE;

			if (Contains(providerClass, "db2") || Contains(providerClass, "ibm"))
				return "DB2";
			if (Contains(providerClass, "mysql"))
				return "MySql";
			if (Contains(providerClass, "odbc"))
				return "ODBC";
			if (Contains(providerClass, "oledb"))
				return "OleDb";
			if (Contains(providerClass, "oracle"))
				return "Oracle";
			if (Contains(providerClass, "npgsql") || Contains(providerClass, "postgre"))
				return "PostgreSql";
			if (Contains(providerClass, "sqlclient") || Contains(providerClass, "sqlserverce"))
				return "SqlServer";

			return "";
		}

        ///<summary>
        /// Determine which known vendor this is.
        ///</summary>
        /// <param name="name">The friendly name of the provider (vendor name). This determines the provider for access,
        /// excel, and sharepoint list.</param>
        /// <param name="description">The description of this provider (optional).</param>
        /// <param name="providerClass">The class of the provider. Must not be null. This determines the provider for most vendors except
        /// access, excel, and sharepoint list.</param>
        ///<returns>DB2, MySql, ODBC, OleDb, Oracle, PostgreSql, SqlServer, Access, Excel, SPListOleDb, SPList, or null</returns>
        public static string DetermineProvider(string name, string description, string providerClass)
        {
            if (name == "Access (ODBC)")
                return "Access";
            if (name == "Excel (OleDb)")
                return "Excel";
            if (name == "SharePoint List (OleDb)")
                return "SPListOleDb";
            if (name == "SharePoint List")
                return WrSPListVendor2.PROVIDER_SUBTYPE;

            if (Contains(providerClass, "db2") || Contains(providerClass, "ibm"))
                return "DB2";
            if (Contains(providerClass, "mysql"))
                return "MySql";
            if (Contains(providerClass, "odbc"))
                return "ODBC";
            if (Contains(providerClass, "oledb"))
                return "OleDb";
            if (Contains(providerClass, "oracle"))
                return "Oracle";
            if (Contains(providerClass, "npgsql") || Contains(providerClass, "postgre"))
                return "PostgreSql";
            if (Contains(providerClass, "sqlclient") || Contains(providerClass, "sqlserverce"))
                return "SqlServer";

            return "";
        }
        /// <summary>
        /// Returns true if provider contains name.
        /// </summary>
        /// <param name="provider">Provider string.  Must not be null.</param>
        /// <param name="name"></param>
        /// <returns></returns>
		private static bool Contains(string provider, string name)
		{
			provider = provider.ToLower();
			if ((provider == name) || provider.StartsWith(name + '.') || provider.EndsWith('.' + name) || provider.Contains('.' + name + '.'))
				return true;
			name += "client";
			return provider.StartsWith(name + '.') || provider.EndsWith('.' + name) || provider.Contains('.' + name + '.');
		}

		/// <summary>
        /// <para>Creates a provider wrapping the ADO.NET generated provider.</para>
        /// <para>See the <see cref="DbProviderFactories.GetFactoryClasses"/> method. It returns a DataTable that contains
        /// the information about each provider factory. <paramref name="providerClass"/> maps to the InvariantName column, which
        /// is for programmatic reference to the data provider. However, for access, excel, and sharepoint list vendors, 
        /// the vendor name is used instead.</para>
        /// </summary>
        /// <param name="providerClass">Usually the DB provider class. Use vendor name for access, excel, and sharepoint list.</param>
        /// <returns></returns>
        public static WrVendor CreateProvider(string providerClass)
        {
            /*
             * Create Provider uses the string determined by DetermineProvider to identify the
             * provider and determine the appropriate WrVendor to generate.  For Access, Excel,
             * and SharePoint list data sources, this string corresponds to WrVendor.ProviderSubtype.
             */

			switch (DetermineProvider(providerClass))
			{
				case "Access":
                    return new WrAccessVendor(WrAccessVendor.FactoryName, null, "System.Data.Odbc");
				case "DB2":
					return new WrDB2Vendor(WrDB2Vendor.FactoryName, null, providerClass);
                case "Excel":
                    return new WrExcelVendor(WrExcelVendor.FactoryName, null, "System.Data.OleDb");
				case "MySql":
					return new WrMySqlVendor(WrMySqlVendor.FactoryName, null, providerClass);
				case "ODBC":
					return new WrOdbcVendor(WrOdbcVendor.FactoryName, null, providerClass);
				case "OleDb":
					return new WrOleDbVendor(WrOleDbVendor.FactoryName, null, providerClass);
				case "Oracle":
					return new WrOracleVendor(WrOracleVendor.FactoryName, null, providerClass);
				case "PostgreSql":
					return new WrPostgreSqlVendor(WrPostgreSqlVendor.FactoryName, null, providerClass);
                case WrSPListVendor2.PROVIDER_SUBTYPE:
                    return new WrSPListVendor2(WrSPListVendor2.FactoryDispName, null, WrSPListVendor2.FactoryClass);
                case "SPListOleDb":
                    return new WrSPListVendor(WrSPListVendor.FactoryName, null, "System.Data.OleDb");
				case "SqlServer":
					return new WrSqlServerVendor(WrSqlServerVendor.FactoryName, null, providerClass);
            }
            Trap.trap();
			return new WrUnknownVendor(providerClass.Substring(providerClass.LastIndexOf('.') + 1).Trim(), null, providerClass);
        }

        #endregion

		#region classes this returns

		/// <summary>
		/// All providers on the system, good ones and mis-configured ones.
		/// </summary>
		public class ProviderInfo
		{
			private readonly WrVendor[] vendors;
			private readonly BadProvider[] badProviders;

			internal ProviderInfo(WrVendor[] vendors, BadProvider[] badProviders)
			{
				this.vendors = vendors;
				this.badProviders = badProviders;
			}
			/// <summary>
			/// All properly configured providers.
			/// </summary>
			public WrVendor[] Vendors
			{
				get { return vendors; }
			}
			/// <summary>
			/// All mis-configured providers.
			/// </summary>
			public BadProvider[] BadProviders
			{
				get { return badProviders; }
			}
		}

		/// <summary>
		/// A provider that is mis-configured.
		/// </summary>
		public class BadProvider
		{
			private readonly string providerClass;
			private readonly Exception exception;

			internal BadProvider(string providerClass, Exception exception)
			{
				this.providerClass = providerClass;
				this.exception = exception;
			}
			/// <summary>
			/// The class of the misconfigured provider.
			/// </summary>
			public string ProviderClass
			{
				get { return providerClass; }
			}
			/// <summary>
			/// The configuration error.
			/// </summary>
			public Exception Exception
			{
				get { return exception; }
			}
		}

		#endregion

	}
}
